#include "editorarea.h"

#include "../events/focusevent.h"
#include "../events/saveevent.h"

#include <editormanager.h>
#include <iconmanager.h>

#include <QtCore/QEvent>
#include <QtGui/QAction>
#include <QtGui/QTabBar>

using namespace core;
using namespace gui;

typedef QList<QSharedPointer<IEditor> > EditorList;

EditorArea::EditorArea(QWidget* parent) : QTabWidget(parent), IEditorArea(),
    m_openedEditors(),
    m_saveCurrentDocumentAction(0),
    m_editorFocused(false)
{
    setTabsClosable(true);
    setFocusPolicy(Qt::StrongFocus);

    createActions();
    connectSlots();
}

EditorArea::~EditorArea()
{

}

uint EditorArea::openedEditorsCount() const
{
    uint ret = 0;

    foreach (const EditorList& list, m_openedEditors)
    {
        ret += list.size();
    }

    return ret;
}

QList<QSharedPointer<IEditor> > EditorArea::openedEditors(
    const QSharedPointer<core::IDocument>& document) const
{
    EditorList ret;

    if (m_openedEditors.contains(document))
    {
        ret = m_openedEditors[document];
    }

    return ret;
}

bool EditorArea::event(QEvent* event)
{
    bool ret = false;

    if (event->type() == SaveEvent::type() &&
        (hasFocus() || childrenHasFocus()))
    {
        saveCurrentDocument();
        event->accept();
        ret = true;
    }
    else if (event->type() == FocusEvent::type())
    {
        FocusEvent* _event = dynamic_cast<FocusEvent* >(event);
        m_editorFocused = _event->focusIn();
        event->accept();
        ret = true;
    }
    else
    {
        ret = QTabWidget::event(event);
    }

    return ret;
}

void EditorArea::createActions()
{
    m_saveCurrentDocumentAction = new QAction(this);
    //m_saveCurrentDocumentAction->setShortcut(QKeySequence::Save);

    addAction(m_saveCurrentDocumentAction);
}

void EditorArea::connectSlots()
{
    connect(this, SIGNAL(currentChanged(int)),
            this, SLOT(editorChange(int)));
    connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));

    connect(m_saveCurrentDocumentAction, SIGNAL(triggered()),
        this, SLOT(saveCurrentDocument()));
}

bool EditorArea::childrenHasFocus() const
{
    const bool ret = tabBar()->hasFocus() || m_editorFocused;

    return ret;
}

QSharedPointer<IEditor> EditorArea::currentEditor() const
{
    QSharedPointer<IEditor> ret;

    foreach (const EditorList& list, m_openedEditors)
    {
        foreach (const QSharedPointer<IEditor>& editor, list)
        {
            if (currentWidget() == editor->widget())
            {
                ret = editor;
                break;
            }
        }

        if (!ret.isNull())
        {
            break;
        }
    }

    return ret;
}

int EditorArea::tabFor(const QSharedPointer<core::IEditor>& editor) const
{
    return indexOf(editor->widget());
}

// public slots
void EditorArea::openEditor(const QSharedPointer<IDocument>& document)
{
    if (m_openedEditors.contains(document))
    {
        setCurrentWidget(m_openedEditors[document].at(0)->widget());
    }
    else
    {
        QSharedPointer<IEditor> editor = EditorManager::getInstance()->editor(
            document->mimeType());

        if (!editor.isNull())
        {
            editor->open(document);

            m_openedEditors[document].append(editor);

            const int index = addTab(editor->widget(),
                IconManager::getInstance()->icon(
                    document->mimeType().iconName()),
                        QString("%1%2").arg(document->name()).arg(
                            document->isDirty() ? "*" : ""));

            connect(document.data(), SIGNAL(dirtyChanged(bool)),
                this, SLOT(ensureEditorNameConsistency(bool)));
            connect(document.data(), SIGNAL(nameChanged()),
                this, SLOT(ensureEditorNameConsistency()));

            editor->registerFocusListener(this);

            setCurrentIndex(index);

            emit editorOpened(editor);
        }
    }
}

void EditorArea::closeEditor(const QSharedPointer<IEditor>& editor)
{
    QSharedPointer<IDocument> document;

    if (m_openedEditors.contains(editor->document()))
    {
        if (m_openedEditors[editor->document()].contains(editor))
        {
            document = editor->document();
        }
    }

    if (!document.isNull())
    {
        emit aboutToClose(editor);
    }
}

void EditorArea::confirmClose(const QSharedPointer<IEditor>& editor)
{
    if (m_openedEditors.contains(editor->document()))
    {
        if (m_openedEditors[editor->document()].contains(editor))
        {
            for (int i = 0; i < count(); ++i)
            {
                if (widget(i) == editor->widget())
                {
                    removeTab(i);

                    disconnect(editor->document().data(),
                        SIGNAL(dirtyChanged(bool)),
                        this, SLOT(ensureEditorNameConsistency(bool)));
                    disconnect(editor->document().data(),
                        SIGNAL(nameChanged()),
                        this, SLOT(ensureEditorNameConsistency()));

                    editor->unregisterFocusListener(this);

                    m_openedEditors.remove(editor->document());                    

                    break;
                }
            }
        }
    }
}

// private slots
void EditorArea::editorChange(int index)
{
    QSharedPointer<IEditor> editor = currentEditor();

    emit currentChanged(editor); // can be null
}

void EditorArea::closeTab(int index)
{
    QSharedPointer<IEditor> editor;

    foreach (const EditorList& list, m_openedEditors)
    {
        foreach (const QSharedPointer<IEditor>& _editor, list)
        {
            if (indexOf(_editor->widget()) == index)
            {
                editor = _editor;
                break;
            }
        }
    }

    Q_ASSERT(!editor.isNull());

    closeEditor(editor);
}

void EditorArea::ensureEditorNameConsistency(bool isDirty)
{
    foreach (const QSharedPointer<IDocument>& document,
        m_openedEditors.keys())
    {
        const QList<QSharedPointer<IEditor> > editors =
            m_openedEditors[document];

        foreach (const QSharedPointer<IEditor>& editor, editors)
        {
            const int index = tabFor(editor);

            if (index != -1)
            {
                setTabText(index,
                    QString("%1%2").arg(document->name()).arg(
                               document->isDirty() ? "*" : ""));
            }
        }
    }
}

void EditorArea::saveCurrentDocument()
{
    QSharedPointer<IEditor> editor = currentEditor();

    if (!editor.isNull())
    {
        emit aboutToSave(editor->document());
    }
}
